<?php

class T4SimpleSQLEntity extends T4Node
{
# SQL Scheme configuration
protected $sSchemeTable;
protected $aSchemeFields = array();
protected $sSchemeKey = "id";
protected $sSchemeExtras = "";
protected $sSchemeName = "name";

protected $aRelations = array();
	
# Attributes for Items values
public $mItemId = false;
public $aItemData = array();

/**
 * Сохранение параметров по которым идет выборка/фильтрация
 * @var mixed[string] (struct)
 */
public $uPrototype = array();

# UPC config
protected $UPCAliases = array(
	"new" => "edit"
	);

protected function getDefaultKeyScheme() //TODO: static
	{
	return "int(11) not null auto_increment";
	}

/**
 * Installation
 * @return boolean true if succedes false if fails
 */
public function install()
	{
	X4::RequireAccessLevel("developer");
	$sFields = "";
	$sFields .= SQLEF($this->sSchemeKey)." ".$this->getDefaultKeyScheme();
	foreach ($this->aSchemeFields as $sFieldName=>$sFieldConfig)
		$sFields .= ", ".SQLEF($sFieldName)." ".$sFieldConfig;
	$sFields .= ", PRIMARY KEY (".SQLEF($this->sSchemeKey).")";
	if($this->sSchemeExtras)
		$sFields .= ", ".$this->sSchemeExtras;
	$sQuery = "CREATE TABLE ".SQLEF($this->sSchemeTable)." (".$sFields.")";
	return SQLA($sQuery);
	}
	
/**
 * Uninstallation
 * @return boolean true if succedes false if fails
 */
public function uninstall()
	{
	X4::RequireAccessLevel("developer");
	$sQuery = "DROP TABLE ".SQLEF($this->sSchemeTable);
	return SQLA($sQuery);
	}
	
/**
 * Save or create item, data is taken from $_POST
 * @param $Item mixed[string] Item structure
 * @return int Item id on success or false on fails
 */
public function save($Item = NULL)
	{
	//X4::RequireAccessLevel("admin");
	if(!isset($Item))$Item = $_POST;
	if(array_key_exists($this->sSchemeKey,$Item) and $Item[$this->sSchemeKey]) $id=$Item[$this->sSchemeKey];
	$aFields = array_keys($this->aSchemeFields);
	$aAllFields = array_merge(array($this->sSchemeKey),$aFields);
	$Item = array_keys_filter($Item,$aAllFields);
	$sQuery = "
		INSERT INTO ".SQLEF($this->sSchemeTable)." (".SQLEFS(array_keys($Item)).")
		VALUES (".SQLEVS($Item).")
		ON DUPLICATE KEY UPDATE
		".SQLEPS(array_keys_filter($Item,$aFields))."
		";
	$bTable = SQLA($sQuery);
	if(!$bTable) return false;
	if(!isset($id)) $id = mysql_insert_id();
	return $id;
	}
	
public function load($aKeys="",$aFields=NULL,$sExtra="")
	{
	# Fields filtering
	if($aFields and is_array($aFields))
		{
		$aFields = array_intersect($aFields,array_merge(array($this->sSchemeKey),array_keys($this->aSchemeFields)));
		}
	else
		{
		$aFields = NULL;
		}
	# Fields query string
	$sFields = ($aFields) ? SQLEFS($aFields) : "*";
	# Condition query string
	$sCondition = ($aKeys) ? (" WHERE ".SQLEPS($aKeys," AND ")) : "";
	# Scope condition
	if($sScopeCondition = $this->ScopeFilter())
		{
		if($sCondition)
			$sCondition .= " AND ".$sScopeCondition;
		else
			$sCondition = " WHERE ".$sScopeCondition;
		}
	# Building query
	$sQuery = "SELECT $sFields FROM ".SQLEF($this->sSchemeTable).$sCondition."";
	if($sExtra) $sQuery .= " ".$sExtra;
	# Running query
	$aData = SQLS($sQuery);
	# Result
	return $aData;
	}
	
public $uPageOption = array();
	
public function paging($iPage = 1,$uOptions = array())
	{
	$uPaging = array_merge($this->$uPageOptions,$uOptions);
	$nCount = $this->count();
	$uPaging['nCount'] = $nCount;
	$nPageSize = $uPaging['nPageSize'];
	$nPages = ceil($nCount/$nPageSize);
	if(!$nPages)$nPages=1;
	$uPaging['nPages'] = $nPages;
	$iActivePage = $iPage;
	if(!$iActivePage)$iActivePage=1;
	$uPaging['iActivePage'] = $iActivePage;
	$uPaging['iOffset'] = $nPageSize*($iActivePage-1);
	$uPaging['iPageStart'] = $uPaging['iOffset']+1;
	return $uPaging;
	}

/**
 * Select item with key
 * @param $id int Key
 * @return T4Node $this (UPC chainable)
 */
public function id($id,$Request=NULL)
	{
	if(!$id){$Request->Slice(1); $id = $Request->ParseParams($Request->Next());}
	if($aData = $this->load(array($this->sSchemeKey=>$id),"","LIMIT 1"))
		{
		$this->mItemId = $id;
		$this->aItemData = reset($aData);
		return $this;
		}
	else
		X4::Error404();
	}

/**
 * Select item with name
 * @param $sName string Name
 * @return T4Node $this (UPC chainable)
 */
public function name($sName,$Request)
	{
	if(!$sName){$Request->Slice(1); $sName = $Request->ParseParams($Request->Next());}
	if($aData = $this->load(array($this->sSchemeName=>$sName)))
		{
		$this->aItemData = reset($aData);
		$this->mItemId = $this->aItemData[$this->sSchemeKey];
		return $this;
		}
	}
	
// TODO: Rewrite id() and name() through filter()	

/**
 * Фильтра по списку ID
 * @param $aIds
 * @return unknown_type
 */
public function filter($aIds)
	{
	if(!is_array($aIds))$aIds = array($aIds);
	$sScope = $this->Scope();
	switch ($sScope)
		{
		case "general":
			$this->mItemId = $aIds;
			break;
		case "group":
			$this->mItemId = array_intersect($this->mItemId,$aIds);
			break;
		case "single":
			$this->mItemId = (in_array($this->mItemId,$aIds))
				? $this->mItemId
				: array();
		}
	return $this;
	}
/**
 * Фильтр по произвольной маске
 * @param $aConditions
 * @return unknown_type
 */	
public function cfilter($aConditions)
	{
	$sConditions = $this->condition($aConditions);
	$sQuery = "SELECT ".(SQLEF($this->sSchemeKey))." FROM ".(SQLEF($this->sSchemeTable)).$sConditions;
	$aIds = SQLC($sQuery,$this->sSchemeKey);
	return $this->filter($aIds);
	}
	
//TODO: rename method;	
public function condition($aConditions = array())
	{
	if($aConditions)
			{
			foreach($aConditions as $sField=>$mValue)
				{
				if(!is_array($mValue))
					$aConditions[$sField] = SQLEF($sField)." = ".SQLEV($mValue);
				else
					$aConditions[$sField] = SQLEF($sField)." IN (".SQLEVS($mValue).")";
				}	
			}
		if($sScopeCondition = $this->ScopeFilter())
			{
			$aConditions []= $sScopeCondition;
			}
		$sCondition = $aConditions ? " WHERE ".implode(" AND ",$aConditions) : "";
	return $sCondition;
	}
	
public function delete($aConditions = array())
	{
	if(!$this->mItemId) X4::Error404();
	$sQuery = "
		DELETE FROM ".SQLEF($this->sSchemeTable).
		$this->condition($aConditions)."";
	return SQLA($sQuery);
	}
	
public function count($aConditions = array())
	{
	$sQuery = "SELECT COUNT(*) FROM ".SQLEF($this->sSchemeTable).$this->condition($aConditions);
	$nCount = SQLF($sQuery);
	if($nCount===false) return NULL;
	else return $nCount;
	}

/**
 * Return Entity scope based on mItemId type
 * @return string Scope {'general','group','single'}
 */	
public function Scope()
	{
	if($this->mItemId===false)
		return "general";
	elseif(is_array($this->mItemId))
		return "group";
	else
		return "single";
	}

//TODO: Maybe remove $bFullStatement param? but create condition joiner decorator
/**
 * Return SQL condition for filtering items with mItemId
 * @param $bFullStatement bool add 'WHERE' or not
 * @param $sField string field name according mItemId (default = $sSchemeKey)
 * @return string Condition statement
 */	
protected function ScopeFilter($bFullStatement = false, $sField = "")
	{
	# Prepare params
	if(!$sField)$sField = $this->sSchemeKey;
	# Fetching scope
	$Scope = $this->Scope();
	# Constructing condition
	switch($Scope)
		{
		case "general":
			$sFilter = "";
			break;
		case "single":
			$sFilter = SQLEF($sField)."=".SQLEV($this->mItemId);
			break;
		case "group":
			$sFilter = SQLEF($sField)." IN (".SQLEVS($this->mItemId).")";
			break;
		default:
			$sFilter = "1";
		}
	# Constructiong statement
	$sStatement = ($bFullStatement and $sFilter) ? " WHERE " : "";
	$sStatement .= $sFilter; 
	# Result
	return $sStatement;
	}
	
	
/**
 * Получение объекта отношений
 * @param $sName
 * @return unknown_type
 */	
public function relation($sName) //TODO: !ONLY FOR FK-Relations now
	{
	# No such relation
	if(!array_key_exists($sName,$this->aRelations)) return NULL;
	# Relation not built
	if(!($this->aRelations[$sName] instanceof UPC))
		{
		$xRelation = new T4EntityFKRelation();
		$xRelation->build($this->aRelations[$sName]);
		$this->aRelations[$sName] = $xRelation;
		}
	$this->aRelations[$sName]->setup($this->mItemId);
	return $this->aRelations[$sName];
	}
	
/**
 * Decode Field map for load function
 * @param $uMap
 * @param $sKey
 * @return unknown_type
 */	
static protected function decodeFieldsMapContains($uMap,$sKey)
	{
	if(!$uMap) return true; # Load all
	if(!is_array($uMap))$uMap = array($uMap);
	if(in_array($sKey,$uMap)) return true; # Load this attr
	return false;
	}
	
public function data()
	{
	return $this->aItemData;
	}
}
?>